#ifndef PY_DATA_CLASS_H
#define PY_DATA_CLASS_H

#include "data_class.h"

#include "py_object_handler.h"

#include <boost/preprocessor/cat.hpp>
#include <boost/preprocessor/repetition/repeat_from_to.hpp>

#include "numpy_tools.h"

#include "export_python_wrapper.h"

namespace bp = boost::python;
using namespace blue_sky::tools;

namespace blue_sky {
namespace python {

#define REGISTER_PROPERTIES_I(z, n_, t_)                                              \
  BOOST_PP_TUPLE_ELEM (4, 0, t_).add_property (BOOST_PP_TUPLE_ELEM (4, 1, t_)[n_],    \
    &BOOST_PP_TUPLE_ELEM (4, 2, t_) <BOOST_PP_TUPLE_ELEM (4, 3, t_), n_>::get_array,  \
    &BOOST_PP_TUPLE_ELEM (4, 2, t_) <BOOST_PP_TUPLE_ELEM (4, 3, t_), n_>::set_array);

#define REGISTER_PROPERTIES(from_, to_, t_) \
  BOOST_PP_REPEAT_FROM_TO (from_, to_, REGISTER_PROPERTIES_I, t_)

  template <typename T>
  typename T::vec_i &
  get_equil_regions (T *t)
  {
    return t->equil_regions;
  }

  template <typename T>
  int
  get_units_in (T *t)
  {
    return t->input_units_converter.get_input_units ();
  }
  template <typename T>
  int
  get_units_out (T *t)
  {
    return t->output_units_converter.get_output_units ();
  }
  template <typename T>
  void
  set_units_in (T *t, int v)
  {
    t->input_units_converter.set_input_units (v);
  }
  template <typename T>
  void
  set_units_out (T *t, int v)
  {
    t->output_units_converter.set_output_units (v);
  }


  template <typename T>
  void 
  set_float_array (T *t, int cur_index, const boost::python::object &obj)
  {
    bp::ssize_t len   = numpy_tools::get_len (obj);
    float16_t *buffer = numpy_tools::get_buffer <float16_t> (obj, len);

    (*t->d_map)[cur_index].array = shared_array (buffer, len);
    t->subscribe (objbase::on_delete, new py_object_handler (obj.ptr ()));
  }

  template <typename T>
  void 
  set_int_array (T *t, int cur_index, const boost::python::object &obj)
  {
    bp::ssize_t len = numpy_tools::get_len (obj);
    uint8_t *buffer = numpy_tools::get_buffer <uint8_t> (obj, len);

    (*t->i_map)[cur_index].array = shared_array (buffer, len);
    t->subscribe (objbase::on_delete, new py_object_handler (obj.ptr ()));
  }

  template <typename T>
  array_uint8_t
  get_int_array (T *t, int cur_index)
  {
    return t->get_int_non_empty_array (cur_index);
  }
  template <typename T>
  array_float16_t
  get_float_array (T *t, int cur_index)
  {
    return t->get_float_non_empty_array (cur_index);
  }

  template <typename T>
  long long
  get_nx (T *t)
  {
    return t->dimens.nx;
  }
  template <typename T>
  long long
  get_ny (T *t)
  {
    return t->dimens.ny;
  }
  template <typename T>
  long long
  get_nz (T *t)
  {
    return t->dimens.nz;
  }


  PY_EXPORTER (idata_exporter, default_exporter)
    .def("init",                          &T::init)
    .add_property ("rpo_model",           &T::rpo_model)
    .add_property ("minimal_pore_volume", &T::minimal_pore_volume)
    .add_property ("nx",                  get_nx <T>)
    .add_property ("ny",                  get_ny <T>)
    .add_property ("nz",                  get_nz <T>)
    .add_property ("dimens",              &T::dimens)
    .add_property ("pvt_region",          &T::pvt_region)
    .add_property ("sat_region",          &T::sat_region)
    .add_property ("eql_region",          &T::eql_region)
    .add_property ("fip_region",          &T::fip_region)
    .add_property ("fi_n_phase",          &T::fi_n_phase)
    .add_property ("fi_phases",           &T::fi_phases)
    .add_property ("rock_region",         &T::rock_region)
    .add_property ("equil_regions",       make_function (get_equil_regions <T>, return_value_policy<reference_existing_object> ()))
    .add_property ("units_in",            get_units_in <T>,  set_units_in <T>)
    .add_property ("units_out",           get_units_out <T>, set_units_out <T>)
    .add_property ("title",               &T::title)
    .add_property ("int_array",           get_int_array   <T>, set_int_array <T>)
    .add_property ("float_array",         get_float_array <T>, set_float_array <T>)
    .def ("get_int_array",                get_int_array   <T>)
    .def ("get_float_array",              get_float_array <T>)
    .def ("set_int_array",                set_int_array   <T>)
    .def ("set_float_array",              set_float_array <T>)
    .def ("get_float_array",              &T::get_float_array)
    .def ("get_int_array",                &T::get_int_array)
  PY_EXPORTER_END;


  /**
   * \brief Exports shared_vectors and idata to python
   * */
  void 
  py_export_idata();

} // namespace python
} // namespace blue_sky

#endif // PY_DATA_CLASS_H

